package net.wantongtech.xa.uploadbasicinfo.service;

import cn.com.taiji.common.manager.net.http.binclient.ApiRequestException;
import cn.com.taiji.sdk.comm.LwClientHelper;
import cn.com.taiji.sdk.model.comm.protocol.sdts.backlist.CardBlackListAllDownloadRequest;
import cn.com.taiji.sdk.model.comm.protocol.sdts.backlist.CardBlackListDownloadModel;
import cn.com.taiji.sdk.model.comm.protocol.sdts.backlist.CardBlackListIncrDownloadRequest;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.wtkj.exception.BusinessException;
import com.zgglyun.common.model.comm.protocol.AbstractBinRequest;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.wantongtech.xa.uploadbasicinfo.dao.ReFillCardBlackListRecordDao;
import net.wantongtech.xa.uploadbasicinfo.vo.CardBlackFileEntry;
import net.wantongtech.xa.uploadbasicinfo.vo.valid.BlackListValidateAbstractModel;
import net.wantongtech.xa.uploadbasicinfo.vo.valid.ProvinceData;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * 重新填充卡片黑名单数据服务实现
 *
 * @author MaShibo
 * 2021/2/20 0020
 */
@Slf4j
@Service
public class ReFillCardBlackListRecordService {

    /**
     * 黑名单校验文件名关键字
     */
    private static final String VALIDATE_JSON_FILE = "_validate.json";

    /**
     * 陕西黑名单文件名关键字
     */
    private static final String SX_PROVINCE_ZIP = "_61.zip";

    private static final int ISSUE_BLACK_LIST = 1;

    private ObjectMapper objectMapper = new ObjectMapper();

    @Getter
    private List<String> validateErrorMsgList = Lists.newArrayList();

    @Getter
    @Setter
    private String savePath;

    @Resource
    private ReFillCardBlackListRecordDao reFillCardBlackListRecordDao;

    @Resource
    private ThreadPoolTaskExecutor taskExecutor;

    public void fill() throws IOException, ZipException, ExecutionException, InterruptedException {

        File savePathDir = new File(this.savePath);
        if (!savePathDir.exists() && !savePathDir.mkdirs()) {
            log.error("黑名单保存文件夹：{}，创建失败", this.savePath);
            return;
        }

        LocalDate nowDate = LocalDate.now();
        String fullVersion = nowDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));

        //删除存储目录中非当日版本的文件
        File[] delFileArr = savePathDir.listFiles((dir, name) -> !name.startsWith(fullVersion) || !name.endsWith(".zip"));
        if (delFileArr != null && delFileArr.length > 0) {
            for (File delFile : delFileArr) {
                log.info("删除：{}", delFile);
                FileSystemUtils.deleteRecursively(delFile);
            }
        }


        LocalDateTime nowDateTime = LocalDateTime.now();


        String firstIncVersion = fullVersion + "0005";

        DateTimeFormatter incVersionFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
        LocalDateTime firstDateTime = LocalDateTime.parse(firstIncVersion, incVersionFormatter);


        List<String> allVersionList = Lists.newArrayList();
        LocalDateTime tmpDateTime = firstDateTime;
        while (tmpDateTime.isBefore(nowDateTime)) {
            String incVersion = tmpDateTime.format(incVersionFormatter);
            allVersionList.add(incVersion);
            tmpDateTime = tmpDateTime.plusMinutes(5);
        }

        allVersionList.add(fullVersion);

        String latestVersion = reFillCardBlackListRecordDao.queryLatestCardBlackVersion();

        if (StringUtils.isNotBlank(latestVersion)) {
            log.info("卡片黑名单已入库的最大版本号为：{}", latestVersion);
            if (latestVersion.compareTo(fullVersion) < 0) {
                log.info("临时卡片黑名单数据已过期，清除表中所有数据...");
                reFillCardBlackListRecordDao.truncateCardRecordTable();
            }
            allVersionList = allVersionList.stream()
                    .filter(version -> version.compareTo(latestVersion) > 0)
                    .collect(Collectors.toList());
        }

        List<String> downloadedVersionList =
                Arrays.stream(Objects.requireNonNull(savePathDir
                        .listFiles((dir, name) -> StringUtils.endsWith(name, ".zip"))))
                        .map(file -> file.getName().substring(0, file.getName().lastIndexOf('.')))
                        .collect(Collectors.toList());


        List<String> missingVersionList = ListUtils.subtract(allVersionList, downloadedVersionList);

        missingVersionList.sort(Comparator.naturalOrder());
        log.info("缺失尚未下载的黑名单版本：{}", missingVersionList);


        for (String version : missingVersionList) {
            log.info("开始下载版本号为：{}的卡片黑名单文件...", version);
            downloadCardBlackFileByVersion(version);
        }

        //筛选需要处理的卡片黑名单文件
        List<String> finalAllVersionList = allVersionList;
        List<CardBlackFileEntry> cardBlackFileEntryList =
                Arrays.stream(Objects.requireNonNull(new File(this.savePath).listFiles((dir, name) -> name.endsWith(".zip"))))
                        .map(file -> {
                            String fileName = file.getName();
                            int suffixIdx = fileName.lastIndexOf('.');
                            String version = fileName.substring(0, suffixIdx);
                            if (finalAllVersionList.contains(version)) {
                                return new CardBlackFileEntry(version, file, version.length() == 12);
                            }
                            return null;
                        })
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList());


        for (CardBlackFileEntry cardBlackFileEntry : cardBlackFileEntryList) {
            processEachVersionFile(cardBlackFileEntry);
        }

    }

    private void processEachVersionFile(CardBlackFileEntry cardBlackFileEntry) throws ZipException, IOException, ExecutionException, InterruptedException {

        boolean inc = cardBlackFileEntry.isInc();
        File file = cardBlackFileEntry.getFile();
        String version = cardBlackFileEntry.getVersion();

        log.info("开始处理版本号为：{}的卡片黑名单文件", version);

        String storePath = file.getAbsolutePath();
        String firstExtractPath = storePath.substring(0, storePath.lastIndexOf('.'));

        extractZipFile(file, firstExtractPath);

        File firstDir = new File(firstExtractPath);

        File[] firstDirFiles = firstDir.listFiles((dir, name) -> name.contains(SX_PROVINCE_ZIP));

        File[] jsonFiles = null;
        if (ArrayUtils.isEmpty(firstDirFiles)) {
            log.warn("未获取到陕西卡片黑名单文件，跳过处理");
        } else {
            File firstDirFile = firstDirFiles[0];
            jsonFiles = extractBlackListJsonFiles(firstDirFile);
            if (ArrayUtils.isEmpty(jsonFiles)) {
                throw new BusinessException("未获取到卡片黑名单json文件");
            }
        }


        try {

            if (ArrayUtils.isNotEmpty(jsonFiles)) {
                //多线程加载处理卡片黑名单json文件
                loadCardBlackJsonFilesByThreads(inc, version, jsonFiles);
            }

            if (!inc) {
                return;
            }

            //验证增量黑名单文件
            checkValidateFile(firstDir);

        } finally {
            log.info("删除文件夹：{}", firstExtractPath);
            FileSystemUtils.deleteRecursively(firstDir);
        }

    }

    private void loadCardBlackJsonFilesByThreads(boolean inc, String version, File[] jsonFiles) throws InterruptedException, ExecutionException {
        List<Future<Void>> futureList = Lists.newArrayList();

        for (File jsonFile : jsonFiles) {

            Future<Void> future = taskExecutor.submit(() -> {
                //从json文件加载卡片黑名单数据
                loadCardBlackRecordFromJsonFile(version, inc, jsonFile);
                return null;
            });

            futureList.add(future);
        }

        for (Future<Void> future : futureList) {
            future.get();
        }
    }

    private void loadCardBlackRecordFromJsonFile(String version, boolean isInc, File jsonFile) throws IOException {
        log.info("开始加载处理卡片黑名单json文件：{}", jsonFile.getAbsolutePath());

        String json = jsonFile2String(jsonFile);
        List<CardBlackListDownloadModel> modelList =
                objectMapper.readValue(json, new TypeReference<List<CardBlackListDownloadModel>>() {
                });

        List<CardBlackListDownloadModel> deleteModelList = Lists.newArrayList();
        List<CardBlackListDownloadModel> insertModelList = Lists.newArrayList();

        if (isInc) {
            for (CardBlackListDownloadModel model : modelList) {
                Integer status = model.getStatus();
                //下发黑名单
                if (status == ISSUE_BLACK_LIST) {
                    insertModelList.add(model);
                    //解除黑名单
                } else {
                    deleteModelList.add(model);
                }
            }
        } else {
            insertModelList = modelList;
        }


        long start;
        if (!deleteModelList.isEmpty()) {
            log.info("删除已解除的卡片黑名单数据...");
            start = System.currentTimeMillis();
            reFillCardBlackListRecordDao.batchDeleteCardBlackRecord(deleteModelList);
            log.info("删除完成，耗时：{}ms", System.currentTimeMillis() - start);
        }


        if (!insertModelList.isEmpty()) {
            log.info("插入要下发的卡片黑名单数据...");
            start = System.currentTimeMillis();
            reFillCardBlackListRecordDao.batchInsertCardBlackRecord(insertModelList, version);
            log.info("插入完成，耗时：{}ms", System.currentTimeMillis() - start);
        }
    }

    private void checkValidateFile(File firstDir) throws IOException {
        File[] validateJsonFiles = firstDir.listFiles((dir, name) -> name.contains(VALIDATE_JSON_FILE));
        Validate.isTrue(validateJsonFiles != null && validateJsonFiles.length == 1, "未获取到解压后的黑名单校验文件");
        File validateJsonFile = validateJsonFiles[0];
        String validateJson = jsonFile2String(validateJsonFile);
        BlackListValidateAbstractModel validateModel = objectMapper.readValue(validateJson, BlackListValidateAbstractModel.class);
        List<ProvinceData> provinceDataList = validateModel.getData();
        provinceDataList.stream()
                .filter(provinceData -> "61".equals(provinceData.getProvince()))
                .findFirst()
                .ifPresent(provinceData -> {
                    Integer centerCount = provinceData.getCount();
                    int dbCount = reFillCardBlackListRecordDao.queryCardBlackRecordCount();
                    if (centerCount != dbCount) {
                        String msg = String.format("部中心校验文件（%s）卡片黑名单数量：%d，与卡片黑名单表中数量：%d，不一致",
                                validateJsonFile.getName(), centerCount, dbCount);
                        this.validateErrorMsgList.add(msg);
                    }
                });
    }

    private void downloadCardBlackFileByVersion(String version) {
        boolean isInc = version.length() == 12;
        boolean renameResult;
        File downloadFile;
        if (isInc) {
            CardBlackListIncrDownloadRequest incDownloadReq = new CardBlackListIncrDownloadRequest();
            incDownloadReq.setVersion(version);
            LwClientHelper.prepareFilename(incDownloadReq);
            downloadFile = downloadFileUntilSuccess(incDownloadReq);

        } else {
            CardBlackListAllDownloadRequest fullDownloadReq = new CardBlackListAllDownloadRequest();
            fullDownloadReq.setVersion(version);
            LwClientHelper.prepareFilename(fullDownloadReq);
            downloadFile = downloadFileUntilSuccess(fullDownloadReq);
        }

        if (downloadFile == null) {
            return;
        }

        String fileName = downloadFile.getName();
        String fileSuffix = fileName.substring(fileName.lastIndexOf('.'));
        String newFileName = String.format("%s%s", version, fileSuffix);
        File newFile = new File(downloadFile.getParent(), newFileName);
        renameResult = downloadFile.renameTo(newFile);
        if (!renameResult) {
            log.error("文件：{}，重命名为：{}，失败", downloadFile.getName(), newFileName);
            throw new BusinessException(String.format("文件：%s，重命名为：%s，失败", downloadFile.getName(), newFileName));

        }
    }

    public File downloadFileUntilSuccess(AbstractBinRequest request) {
        try {
            return LwClientHelper.downLoad(request, savePath);
        } catch (IOException e) {
            log.error("卡片黑名单文件下载网络异常：{}", e.getMessage());
            return downloadFileUntilSuccess(request);
        } catch (ApiRequestException e) {
            log.error(e.getMessage());
            return null;
        }
    }


    private void extractZipFile(File targetFile, String destDir) throws ZipException {
        Validate.notNull(targetFile, "zip文件路径不能为空");
        Validate.notBlank(destDir, "解压路径不能为空");
        ZipFile zipFile = new ZipFile(targetFile);
        zipFile.extractAll(destDir);
    }


    private File[] extractBlackListJsonFiles(File firstDirFile) throws ZipException {
        String firstDirFilePath = firstDirFile.getAbsolutePath();
        //第二层解压路径，包含：202102190935_61_1.json
        String secondExtractPath = firstDirFilePath.substring(0, firstDirFilePath.lastIndexOf('.'));

        extractZipFile(firstDirFile, secondExtractPath);

        return new File(secondExtractPath).listFiles((dir, name) -> name.endsWith(".json"));
    }

    private String jsonFile2String(File jsonFile) throws IOException {
        try (FileInputStream fis = new FileInputStream(jsonFile)) {
            return IOUtils.toString(fis, StandardCharsets.UTF_8.displayName());
        }
    }
}
